Skip to content

feat(typescript-angular): update enum definitions to use 'as const' for improved type safety#20958

Merged
macjohnny merged 1 commit intoOpenAPITools:masterfrom
daniel-sc:20809-typescript-angular-enums-as-const
Mar 24, 2025
Merged

feat(typescript-angular): update enum definitions to use 'as const' for improved type safety#20958
macjohnny merged 1 commit intoOpenAPITools:masterfrom
daniel-sc:20809-typescript-angular-enums-as-const

Conversation

@daniel-sc
Copy link
Copy Markdown
Contributor

fixes #20809

The description of #20809 details the change :)

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in Git BASH)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release - breaking changes with fallbacks), 8.0.x (breaking changes without fallbacks)
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@daniel-sc
Copy link
Copy Markdown
Contributor Author

@macjohnny
Copy link
Copy Markdown
Member

thanks, this should be backwards compatible, right?

@macjohnny
Copy link
Copy Markdown
Member

and whats the advantage of this change? the type union of the string values should already allow the compiler to check for valid values, no?

@daniel-sc
Copy link
Copy Markdown
Contributor Author

@macjohnny yes, this is backwards compatible as the enum values have narrower type.

The change of the enum type:

-export type MyEnum = 'valueA' | 'valueB';
+export type MyEnum = typeof MyEnum [keyof typeof MyEnum];

is not really relevant as both are equivalent - I just though it would be good to have a similar approach as typescript-fetch and typescript-axios.

The main improvement is, that for the following generated enum:

export const MyEnum = {
    ValueA: 'valueA',
    ValueB: 'valueB',
} as const;

the compiler can infer that MyEnum.ValueA is of type 'valueA', wheras before only the type 'valueA' | 'valueB' was inferred.

Subsequently this for example allows for correct "exhaustive check" in switch statements etc.

This is as well the official typescript recommendation: https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums

@macjohnny macjohnny merged commit 1655275 into OpenAPITools:master Mar 24, 2025
15 checks passed
@wing328 wing328 added this to the 7.13.0 milestone Apr 27, 2025
@ReneZeidler
Copy link
Copy Markdown
Contributor

Just wanted to note that this is not fully backwards compatible due to different behaviors with type inference.

Playground Link

// Old behavior
export type MyEnum1 = "valueA" | "valueB" | "valueC";
export const MyEnum1 = {
    ValueA: "valueA" as MyEnum1,
    ValueB: "valueB" as MyEnum1,
    ValueC: "valueC" as MyEnum1,
};

function checkValue1(value: MyEnum1) {
  const allowableValues = [MyEnum1.ValueA, MyEnum1.ValueB]; // inferred type: MyEnum1[]
  return allowableValues.includes(value); // ok
}

// New behavior
export const MyEnum2 = {
    ValueA: "valueA",
    ValueB: "valueB",
    ValueC: "valueC",
} as const;
export type MyEnum2 = typeof MyEnum2[keyof typeof MyEnum2];

function checkValue2(value: MyEnum2) {
  const allowableValues = [MyEnum2.ValueA, MyEnum2.ValueB]; // inferred type: ("valueA" | "valueB")[]
  return allowableValues.includes(value); // error: Argument of type 'MyEnum2' is not assignable to parameter of type '"valueA" | "valueB"'.
}

// Native TypeScript enums
export enum MyEnum3 {
    ValueA = "valueA",
    ValueB = "valueB",
    ValueC = "valueC",
}

function checkValue3(value: MyEnum3) {
  const allowableValues = [MyEnum3.ValueA, MyEnum3.ValueB]; // inferred type: MyEnum3[]
  return allowableValues.includes(value); // ok
}

The solution of course is to explicitly declare the type of allowableValues instead of relying on type inference. It is nevertheless a difference in behavior to the previous implementation and the native TypeScript enum behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ] [typescript-angular] option for using object enums as const

4 participants